#include "http.h"
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include "client.h"
#include "log.h"
#include "io.h"


#define HTTP_HEAD_CHUNK_LEN 1024
#define HTTP_HEAD_MAX_LEN   (1024 * 64)

#define LF                  '\n'
#define CR                  '\r'
#define CRLF                "\r\n"

#define strcmp7(s, c0, c1, c2, c3, c4, c5, c6) \
    s[0] == c0 && s[1] == c1 && s[2] == c2 && s[3] == c3 && s[4] == c4 && s[5] == c5 && s[6] == c6

enum http_parse_ret_code {
    HPR_ERROR = -1,
    HPR_FINISH = 0,
    HPR_NEEDMORE = 1,
    HPR_HEADER_END = 2,
};

enum http_parse_state {
    /* request line */
    hps_r_start,
    hps_r_method,
    hps_r_spaces_before_uri,
    hps_r_schema,
    hps_r_schema_slash,
    hps_r_schema_slash_slash,
    hps_r_host_start,
    hps_r_host,
    hps_r_host_end,
    hps_r_host_ip_literal,
    hps_r_port,
    hps_r_host_http_09,
    hps_r_after_slash_in_uri,
    hps_r_check_uri_http_09,
    hps_r_uri,
    hps_r_http_09,
    hps_r_http_proto,
    hps_r_almost_done,

    /* header line */
    hps_h_start,
    hps_h_name,
    hps_h_space_before_value,
    hps_h_value,
    hps_h_space_after_value,
    hps_h_ignore_line,
    hps_h_almost_done,
    hps_h_header_almost_done,
};

/******* http response defile ************************************************/

#define HTTP_VER  "HTTP/1.1 "

#if __SIZEOF_SIZE_T__ == __SIZEOF_INT__
#  define HTTP_CONTENT_HEADER  \
        "Content-Type: text/html" CRLF \
        "Content-Length: %u" CRLF CRLF
#else
#  define HTTP_CONTENT_HEADER  \
        "Content-Type: text/html" CRLF \
        "Content-Length: %lu" CRLF CRLF
#endif

#define HTTP_ERROR_PAGE \
        "<html>" CRLF \
        "<head><title>%s</title></head>" CRLF \
        "<body bgcolor=\"white\">" CRLF \
        "<center><h1>%s</h1></center>" CRLF \
        "<hr><center>proxy</center>" CRLF \
        "</body>" CRLF \
        "</html>" CRLF

#define C200 HTTP_VER "200 OK" CRLF CRLF
#define C400 "400 Bad Request"
#define C413 "413 Request Entity Too Large"
#define C500 "500 Internal Server Error"
#define C503 "503 Service Unavailable"

static void http_error_response(struct client *c, const char *code_reason) {
    int n = strlen(code_reason);
    snprintf(c->ctx->inbuf, c->ctx->inbuflen,

            HTTP_VER "%s" CRLF
            HTTP_CONTENT_HEADER
            HTTP_ERROR_PAGE
            "%n",

            code_reason,
            sizeof(HTTP_ERROR_PAGE) - 2*2 - 1 + n*2, // about 2*2 and n*2: have two %s in HTTP_ERROR_PAGE
            code_reason, code_reason,
            &n
        );

    client_write2sock(c->lsock, c->ctx->inbuf, n, c);
}

/******* http response defile end ********************************************/

static struct request *http_header_realloc(struct request *old) {
    struct request *new;

    if (!(new = request_realloc(old))) {
        // XXX: no mem ?
        free(old);
        return NULL;
    }

    if (old == NULL) {
        new->stat = hps_r_start;
        return new;
    }

    ssize_t diff = (void *) new - (void *) old;
    if (diff) {
        if (new->http.request_start)
            new->http.request_start += diff;
        if (new->http.method_end)
            new->http.method_end += diff;
        if (new->http.schema_start)
            new->http.schema_start += diff;
        if (new->http.schema_end)
            new->http.schema_end += diff;
        if (new->http.host0_start)
            new->http.host0_start += diff;
        if (new->http.host0_end)
            new->http.host0_end += diff;
        if (new->http.port_start)
            new->http.port_start += diff;
        if (new->http.port_end)
            new->http.port_end += diff;
        if (new->http.uri_start)
            new->http.uri_start += diff;
        if (new->http.uri_end)
            new->http.uri_end += diff;
        if (new->http.http_protocol_start)
            new->http.http_protocol_start += diff;
        if (new->http.http_protocol_end)
            new->http.http_protocol_end += diff;
        if (new->http.header_name_start)
            new->http.header_name_start += diff;
        if (new->http.header_name_end)
            new->http.header_name_end += diff;
        if (new->http.header_start)
            new->http.header_start += diff;
        if (new->http.header_end)
            new->http.header_end += diff;
        if (new->http.host_start)
            new->http.host_start += diff;
        if (new->http.host_end)
            new->http.host_end += diff;
        if (new->http.proxy_connection_name_start)
            new->http.proxy_connection_name_start += diff;
        if (new->http.proxy_connection_name_end)
            new->http.proxy_connection_name_end += diff;
    }

    return new;
}

/* return:
 * HPR_ERROR for parse error,
 * HPR_FINISH for finish,
 * HPR_NEEDMORE for need more data */
static int parse_request_line(struct request *h) {
    int ch;
    enum http_parse_state state = h->stat;
    char *p = h->buf.pos;
    char *end = h->buf.raw + h->buf.len;

    for (; p < end; ++p) {
        ch = *p;

        switch (state) {
        default:
            return HPR_ERROR;

        case hps_r_start:
            h->http.request_start = p;

            if (ch == CR || ch == LF)
                break;

            if (!isupper(ch) && ch != '_')
                return HPR_ERROR;

            state = hps_r_method;
            break;

        case hps_r_method:
            if (ch == ' ') {
                h->http.method_end = p;
                char *m = h->http.request_start;
                if (p - m == 7 && strcmp7(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T'))
                    h->http.is_connect_method = 1;
                state = hps_r_spaces_before_uri;
                break;
            }

            if (!isupper(ch) && ch != '_')
                return HPR_ERROR;

            break;

        case hps_r_spaces_before_uri:
            if (ch == ' ')
                break;

            if (ch == '/') {
                h->http.uri_start = p;
                state = hps_r_after_slash_in_uri;
                break;
            }

            if (h->http.is_connect_method) {
                --p;
                state = hps_r_host_start;
            } else {
                h->http.schema_start = p;
                state = hps_r_schema;
            }

            break;

        case hps_r_schema:
            if (isalpha(ch))
                break;

            if (ch == ':') {
                h->http.schema_end = p;
                state = hps_r_schema_slash;
            } else
                return HPR_ERROR;

            break;

        case hps_r_schema_slash:
            if (ch != '/')
                return HPR_ERROR;
            state = hps_r_schema_slash_slash;
            break;

        case hps_r_schema_slash_slash:
            if (ch != '/')
                return HPR_ERROR;
            state = hps_r_host_start;
            break;

        case hps_r_host_start:
            h->http.host0_start = p;
            if (ch == '[') {
                state = hps_r_host_ip_literal;
                break;
            }
            state = hps_r_host;
            /* no break */
        case hps_r_host:
            if (isalpha(ch) || isdigit(ch) || ch == '.' || ch == '-')
                break;
            /* no break */
        case hps_r_host_end:
            h->http.host0_end = p;
            switch (ch) {
            case ':':
                h->http.port_start = p + 1;
                state = hps_r_port;
                break;
            case '/':
                h->http.uri_start = p;
                state = hps_r_after_slash_in_uri;
                break;
            case ' ':
                if (h->http.is_connect_method) {
                    h->http.http_protocol_start = p + 1;
                    state = hps_r_http_proto;
                } else {// FIXME:
                    h->http.uri_start = h->http.schema_end + 1;
                    h->http.uri_end = h->http.schema_end + 2;
                    state = hps_r_host_http_09;
                }
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_r_host_ip_literal:
            if (isdigit(ch) || isalpha(ch))
                break;

            switch (ch) {
            case ':':
                break;
            case ']':
                state = hps_r_host_end;
                break;
            case '-': case '.': case '_': case '~':
                /* unreserved */
                break;
            case '!': case '$': case '&': case '\'': case '(': case ')':
            case '*': case '+': case ',': case ';': case '=':
                /* sub-delims */
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_r_port:
            if (isdigit(ch))
                break;

            switch (ch) {
            case '/':
                h->http.port_end = p;
                h->http.uri_start = p;
                state = hps_r_after_slash_in_uri;
                break;
            case ' ':
                h->http.port_end = p;
                if (h->http.is_connect_method) {
                    h->http.http_protocol_start = p + 1;
                    state = hps_r_http_proto;
                } else { // FIXME:
                    h->http.uri_start = h->http.schema_end + 1;
                    h->http.uri_end = h->http.schema_end + 2;
                    state = hps_r_host_http_09;
                }
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_r_host_http_09:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = hps_r_almost_done;
                break;
            case LF:
                goto DONE;
            case 'H':
                h->http.http_protocol_start = p;
                state = hps_r_http_proto;
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_r_after_slash_in_uri:
            switch (ch) {
            case ' ':
                h->http.uri_end = p;
                state = hps_r_check_uri_http_09;
                break;
            case CR:
                h->http.uri_end = p;
                state = hps_r_almost_done;
                break;
            case LF:
                h->http.uri_end = p;
                goto DONE;
            case '\0':
                return HPR_ERROR;
            default:
                state = hps_r_uri;
                break;
            }
            break;

        case hps_r_check_uri_http_09:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = hps_r_almost_done;
                break;
            case LF:
                goto DONE;
            case 'H':
                h->http.http_protocol_start = p;
                state = hps_r_http_proto;
                break;
            default:
                state = hps_r_uri;
                break;
            }
            break;

        case hps_r_uri:
            switch (ch) {
            case ' ':
                h->http.uri_end = p;
                state = hps_r_http_09;
                break;
            case CR:
                h->http.uri_end = p;
                state = hps_r_almost_done;
                break;
            case LF:
                h->http.uri_end = p;
                goto DONE;
            case '\0':
                return HPR_ERROR;
            default:
                break;
            }
            break;

        case hps_r_http_09:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = hps_r_almost_done;
                break;
            case LF:
                goto DONE;
            case 'H':
                h->http.http_protocol_start = p;
                state = hps_r_http_proto;
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_r_http_proto:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = hps_r_almost_done;
                break;
            case LF:
                goto DONE;
            default:
                break;
            }
            break;

        /* end of request line */
        case hps_r_almost_done:
            if (ch != LF)
                return HPR_ERROR;
            goto DONE;
        }
    }

    h->buf.pos = p;
    h->stat = state;
    return HPR_NEEDMORE;

DONE:
    h->buf.pos = p + 1;
    h->http.http_protocol_end = p - 1;
    h->http.header_start = p + 1;
    h->stat = hps_h_start;
    return HPR_FINISH;
}

/* return:
 * HPR_ERROR for parse error,
 * HPR_FINISH for finish,
 * HPR_NEEDMORE for need more data,
 * HPR_HEADER_END for header end */
static int parse_header_line(struct request *h) {
    int ch;
    enum http_parse_state state = h->stat;
    char *p = h->buf.pos;
    char *end = h->buf.raw + h->buf.len;

    for (; p < end; ++p) {
        ch = *p;
        switch (state) {
        default:
            /* Invalid state */
            return HPR_ERROR;
        case hps_h_start:
            h->http.header_name_start = p;
            switch (ch) {
            case CR:
                h->http.header_end = p;
                state = hps_h_header_almost_done;
                break;
            case LF:
                h->http.header_end = p;
                goto header_done;
            default:
                state = hps_h_name;
                if (isalpha(ch) || isdigit(ch) || ch == '_')
                    break;
                return HPR_ERROR;
            }
            break;

        case hps_h_name:
            if (isalpha(ch) || isdigit(ch) || ch == '_' || ch == '-')
                break;
            if (ch == ':') {
                h->http.header_name_end = p;
                state = hps_h_space_before_value;
                break;
            }
            if (ch == CR) {
                h->http.header_name_end = p;
                h->http.header_start = p;
                h->http.header_end = p;
                state = hps_h_almost_done;
                break;
            }
            if (ch == LF) {
                h->http.header_name_end = p;
                h->http.header_start = p;
                h->http.header_end = p;
                goto done;
            }
            /* IIS may send the duplicate "HTTP/1.1 ..." lines */
            if (ch == '/' && p - h->http.header_name_start == 4
                    && strncmp(h->http.header_name_start, "HTTP", 4) == 0) {
                state = hps_h_ignore_line;
                break;
            }
            return HPR_ERROR;

        case hps_h_space_before_value:
            switch (ch) {
            case ' ':
                break;
            case CR:
                h->http.header_start = p;
                h->http.header_end = p;
                state = hps_h_almost_done;
                break;
            case LF:
                h->http.header_start = p;
                h->http.header_end = p;
                goto done;
            case '\0':
                return HPR_ERROR;
            default:
                h->http.header_start = p;
                state = hps_h_value;
                break;
            }
            break;

        case hps_h_value:
            switch (ch) {
            case ' ':
                h->http.header_end = p;
                state = hps_h_space_after_value;
                break;
            case CR:
                h->http.header_end = p;
                state = hps_h_almost_done;
                break;
            case LF:
                h->http.header_end = p;
                goto done;
            case '\0':
                return HPR_ERROR;
            }
            break;

        case hps_h_space_after_value:
            switch (ch) {
            case ' ':
                break;
            case CR:
                state = hps_h_almost_done;
                break;
            case LF:
                goto done;
            case '\0':
                return HPR_ERROR;
            default:
                state = hps_h_value;
                break;
            }
            break;

        case hps_h_ignore_line:
            switch (ch) {
            case LF:
                state = hps_h_start;
                break;
            default:
                break;
            }
            break;

        case hps_h_almost_done:
            switch (ch) {
            case LF:
                goto done;
            case CR:
                break;
            default:
                return HPR_ERROR;
            }
            break;

        case hps_h_header_almost_done:
            switch (ch) {
            case LF:
                goto header_done;
            default:
                return HPR_ERROR;
            }
        }
    }

    h->buf.pos = p;
    h->stat = state;
    return HPR_NEEDMORE;

done:
    h->buf.pos = p + 1;
    h->stat = hps_h_start;
    return HPR_FINISH;

header_done:
    h->buf.pos = p + 1;
    h->stat = hps_h_start;
    return HPR_HEADER_END;
}

/* return:
 * HPR_ERROR for parse error,
 * HPR_FINISH for finish,
 * HPR_NEEDMORE for need more data */
static int parse_necessary_header_line(struct request *h) {
    int n;
    do {
        if ((n = parse_header_line(h)) == HPR_FINISH) {
            if (!h->http.host_end && strncasecmp("Host", h->http.header_name_start,
                    sizeof("Host") - 1) == 0) {
                h->http.host_start = h->http.header_start;
                h->http.host_end = h->http.header_end;
                if (h->http.is_connect_method || h->http.proxy_connection_name_end)
                    return HPR_FINISH;
                continue;
            }
            if (!h->http.proxy_connection_name_end &&
                    strncasecmp("Proxy-Connection", h->http.header_name_start,
                        sizeof("Proxy-Connection") - 1) == 0) {
                h->http.proxy_connection_name_start = h->http.header_name_start;
                h->http.proxy_connection_name_end = h->http.header_name_end;
                if ((h->http.host0_start && h->http.host0_start != h->http.host0_end)
                        || h->http.host_end)
                    return HPR_FINISH;
                continue;
            }
        }
    } while (n == HPR_FINISH);

    return n == HPR_HEADER_END ? HPR_FINISH : n;
}

static void http_header_send_hander(EV_P_ ev_io *w, int revents) {
    struct client *c = w->data;

    char *dst = c->ctx->inbuf;
    size_t dstleft = c->ctx->inbuflen;
    char *src = c->req->buf.pos;
    size_t srcn;
    ssize_t n;

    do {
        char *tmp = NULL;
        if (c->req->http.schema_end) {
            if (src < c->req->http.schema_start)
                tmp = c->req->http.schema_start;
        } else if (c->req->http.host0_end) {
            if (src < c->req->http.host0_start)
                tmp = c->req->http.host0_start;
        }
        if (tmp) {
            srcn = tmp - c->req->buf.pos;
            n = min(dstleft, srcn);
            memcpy(dst, src, n);
            src = n == (ssize_t)srcn ? c->req->http.uri_start : src + n;
            c->req->buf.pos = src;
            dst += n;
            dstleft -= n;
            if (!dstleft)
                break;
        }

        if (src == c->req->http.uri_start &&
                c->req->http.uri_start == c->req->http.schema_end + 1) {
            *dst++ = '/';
            src = c->req->http.http_protocol_start - 1;
            c->req->buf.pos = src;
            if (!--dstleft)
                break;
        }

        if (c->req->http.proxy_connection_name_start
                && src < c->req->http.proxy_connection_name_start) {
            srcn = c->req->http.proxy_connection_name_start - src;
            n = min(dstleft, srcn);
            memcpy(dst, src, n);
            src = n == (ssize_t)srcn ?
                    c->req->http.proxy_connection_name_start + sizeof("Proxy-") - 1 :
                    src + n;
            c->req->buf.pos = src;
            dst += n;
            dstleft -= n;
            if (!dstleft)
                break;
        }

        srcn = c->req->buf.raw + c->req->buf.len - src;
        n = min(dstleft, srcn);
        memcpy(dst, src, n);
        src += n;
        c->req->buf.pos = src;
        dstleft -= n;
    } while (0);

    if ((n = crypt_en(&c->crypt, c->ctx->outbuf, c->ctx->outbuflen,
            c->ctx->inbuf, c->ctx->inbuflen - dstleft)) < 0)
        goto DESTROY;
    if (client_write2sock(w->fd, c->ctx->outbuf, n, c) < 0)
        goto DESTROY;

    if (src - c->req->buf.raw == (ssize_t)c->req->buf.len) {
        ev_io_stop(EV_A_ w);
        ev_io_set(w, w->fd, EV_READ);
        ev_set_cb(w, relay_handler);
        ev_io_start(EV_A_ w);

        ev_set_cb(&c->levio, relay_handler);
        if (!ev_is_active(&c->levio))
            ev_io_start(EV_A_ &c->levio);
    }

    return;

DESTROY:
    http_error_response(c, C500);
    client_destroy(c);
}

static void remote_connected_handler(EV_P_ ev_io *w, int revents) {
    ssize_t n;
    struct client *c = w->data;

    if ((errno = get_socket_errno(w->fd))) {
        client_log_socket_error(LOG_WARNING, c, c->rsock, "connect failure");
        http_error_response(c, C503);
        update_server_weight(c, -1);
        goto DESTROY;
    }

    update_server_weight(c, 1);

    if (crypt_init(&c->crypt, c->srv->crypt_method, c->srv->passwd))
        goto RESPONSE_500;

    /* +------+----------+----------+
     * | ATYP | DST.ADDR | DST.PORT |
     * +------+----------+----------+
     * |  1   | Variable |    2     |
     * +------+----------+----------+ */
    *(char *)(c->ctx->inbuf) = 0x03; // ATYP: DOMAINNAME
    *(char *)(c->ctx->inbuf + 1) = c->req->http.host_end - c->req->http.host_start;
    memcpy(c->ctx->inbuf + 2, c->req->http.host_start,
            c->req->http.host_end - c->req->http.host_start);
    *(in_port_t *) (c->ctx->inbuf + 2
            + (c->req->http.host_end - c->req->http.host_start)) = htons(
            c->req->http.port);

    if ((n = crypt_en(&c->crypt, c->ctx->outbuf, c->ctx->outbuflen,
            c->ctx->inbuf, c->req->http.host_end - c->req->http.host_start + 4)) < 0)
        goto RESPONSE_500;
    if (client_write2sock(w->fd, c->ctx->outbuf, n, c) < 0)
        goto RESPONSE_500;

    if (c->req->http.is_connect_method) {
        if (client_write2sock(c->lsock, C200, sizeof(C200) - 1, c) < 0)
            goto DESTROY;
        ev_io_stop(EV_A_ w);
        ev_io_set(w, w->fd, EV_READ);
        ev_set_cb(w, relay_handler);
        ev_io_start(EV_A_ w);
        ev_set_cb(&c->levio, relay_handler);
        if (!ev_is_active(&c->levio))
            ev_io_start(EV_A_ &c->levio);
    } else {
        c->req->buf.pos = c->req->http.request_start;
        ev_set_cb(w, http_header_send_hander); // FIXME:
    }
    ev_timer_stop(EV_A_ &c->evtimer);
    ev_timer_set(&c->evtimer, 0., c->ctx->cfg->socket_timeout);
    ev_set_cb(&c->evtimer, sweep_handler);
    ev_timer_again(EV_A_ &c->evtimer);

    return;

RESPONSE_500:
    http_error_response(c, C500);
DESTROY:
    client_destroy(c);
}

static void connect_timer_cb(EV_P_ ev_timer *watcher, int revents) {
    struct client *c = watcher->data;
    errno = ETIMEDOUT;
    client_log_socket_error(LOG_WARNING, c, c->rsock, "connect failure");
    http_error_response(c, C503);
    update_server_weight(c, -1);
    client_destroy(c);
}

static void try_connect(struct client *c, const struct addr *addr) {
    union {
        struct sockaddr_in in4;
        struct sockaddr_in6 in6;
    } in;
    socklen_t inlen;
    struct sockaddr *inp = (struct sockaddr *)&in;

    memset(&in, 0, sizeof(in));
    if (addr->addr[0].family == AF_INET) {
        in.in4.sin_family = AF_INET;
        in.in4.sin_addr = addr->addr[0].in4;
        in.in4.sin_port = c->srv->port;
        inlen = sizeof(in.in4);
    } else {
        in.in6.sin6_family = AF_INET6;
        in.in6.sin6_addr = addr->addr[0].in6;
        in.in6.sin6_port = c->srv->port;
        inlen = sizeof(in.in6);
    }

    sockaddr_ntop(inp, c->raddrinfo, sizeof(c->raddrinfo));

    if ((c->rsock = socket(inp->sa_family, SOCK_STREAM, 0)) < 0) {
        LOGW("Cannot create remote socket for %s(%s): %s",
                c->srv->addr, c->raddrinfo, strerror(errno));
        http_error_response(c, C500);
        goto DESTROY;
    }
    nonblocking(c->rsock);
    if (safe_connect(c->rsock, inp, inlen) < 0 && errno != EINPROGRESS) {
        client_log_socket_error(LOG_WARNING, c, c->rsock, "connect failure");
        http_error_response(c, C500);
        update_server_weight(c, -1);
        goto DESTROY;
    }

    ev_io_init(&c->revio, remote_connected_handler, c->rsock, EV_WRITE);
    ev_io_start(c->ctx->evloop, &c->revio);

    ev_timer_set(&c->evtimer, 0., c->ctx->cfg->connect_timeout);
    ev_set_cb(&c->evtimer, connect_timer_cb);
    ev_timer_again(c->ctx->evloop, &c->evtimer);

    return;

DESTROY:
    client_destroy(c);
}

static void server_addr_resolv_cb(const char *domain, const struct addr *result,
        void *udata) {
    struct client *c = udata;

    c->resolv = NULL;
    c->resolv_cb = NULL;

    if (result && result->cnt) {
        try_connect(c, result);
    } else {
        update_server_weight(c, -1);
        http_error_response(c, C503);
        client_destroy(c);
    }
}

static void drop_data_hander(EV_P_ ev_io *w, int revents) {
    struct client *c = w->data;

    if (revents & EV_READ) {
        ssize_t n;
        if ((n = read(w->fd, c->ctx->inbuf, c->ctx->inbuflen)) < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
                return;
            client_log_socket_error(LOG_WARNING, c, w->fd, "recv error");
            client_destroy(c);
        }
        if (!n)
            client_destroy(c);
    }

    if (revents & EV_WRITE)
        client_write_event(EV_A_ w, c);
}

static int header_read_event(EV_P_ ev_io *w, int fd, struct client *c) {
    ssize_t n;
    client_update_activity(c);

    /* we not need recv() if have fresh data */
    if (c->req->buf.pos != c->req->buf.raw) {
        if (c->req->buf.len >= c->req->buf.size) {
            if (c->req->buf.size >= c->ctx->cfg->buf_size) {
                LOGW("%s recv http header error: Header too long", c->laddrinfo);
                http_error_response(c, C413);
                goto DESTROY;
            } else if (!(c->req = http_header_realloc(c->req))) {
                LOGW("%s recv http header error: Not enough memory", c->laddrinfo);
                http_error_response(c, C500);
                goto DESTROY;
            }
        }

        if ((n = read(fd, c->req->buf.raw + c->req->buf.len,
                c->req->buf.size - c->req->buf.len)) < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
                return 0;
            client_log_socket_error(LOG_WARNING, c, fd, "recv error");
            goto DESTROY;
        }
        if (!n)
            goto DESTROY;
        c->req->buf.len += n;
    }

    if (c->req->stat < hps_h_start) {
        /* parse request line */
        if ((n = parse_request_line(c->req)) == HPR_ERROR) {
            LOGW("%s parse http request line error: Invalid request line", c->laddrinfo);
            http_error_response(c, C400);
            goto DESTROY;
        }
        if (n != HPR_FINISH)
            return 0;
    }

    if (!c->req->http.host0_end
            || c->req->http.host0_end == c->req->http.host0_start
            || !c->req->http.is_connect_method) {
        if ((n = parse_necessary_header_line(c->req)) == HPR_ERROR) {
            LOGW("%s parse http header error: Invalid header", c->laddrinfo);
            http_error_response(c, C400);
            goto DESTROY;
        }
        if (n != HPR_FINISH)
            return 0;
    }

    if (!c->req->http.host_end || c->req->http.host_end == c->req->http.host_start) {
        if (!c->req->http.host0_end || c->req->http.host0_end == c->req->http.host0_start) {
            LOGW("%s parse http header error: No host info", c->laddrinfo);
            http_error_response(c, C400);
            goto DESTROY;
        }
        c->req->http.host_start = c->req->http.host0_start;
        c->req->http.host_end = c->req->http.host0_end;
    }

    /*
     * will connect socks server
     * drop request data with CONNECT,
     * otherwise, block local input first.
     */
    if (c->req->http.is_connect_method)
        ev_set_cb(w, drop_data_hander);
    else
        ev_io_stop(EV_A_ w);

    // get port number
    if (c->req->http.port_end
            && c->req->http.port_start != c->req->http.port_end) {
        char *ptr = c->req->http.port_start;
        while (ptr != c->req->http.port_end)
            c->req->http.port = c->req->http.port * 10 + *ptr++ - '0';
    } else {
        if (c->req->http.schema_end
                && c->req->http.schema_end != c->req->http.schema_start)
            if (strcasecmp("https", c->req->http.schema_start) == 0)
                c->req->http.port = 443;
            else
                c->req->http.port = 80;
        else if (c->req->http.is_connect_method)
            c->req->http.port = 443;
        else
            c->req->http.port = 80;
    }

    c->src_proto = SP_HTTP;
    n = c->req->http.host_end - c->req->http.host_start;
    memcpy(c->dst_info, c->req->http.host_start, n);
    snprintf(c->dst_info + n, sizeof(c->dst_info) - n, ":%hu", c->req->http.port);

    LOGI("%s %s --> %s --> %s", src_proto_str(c->src_proto), c->laddrinfo, c->srv->addr, c->dst_info);

    ev_timer_stop(EV_A_ &c->evtimer);

    /* resolve socks server domain */
    c->resolv_cb = server_addr_resolv_cb;
    c->resolv = resolv_query(c->ctx->resolver, c->srv->addr, server_addr_resolv_cb, c);
    if (!c->resolv && errno) {
        c->resolv_cb = NULL;
        client_log_socket_error(LOG_WARNING, c, fd, "resolv_query() error");
        http_error_response(c, C500);
        goto DESTROY;
    }

    return 0;

DESTROY:
    client_destroy(c);
    return -1;
}

void http_header_handler(EV_P_ ev_io *w, int revents) {
    struct client *c = w->data;

    if ((revents & EV_READ) && header_read_event(EV_A_ w, w->fd, c))
        return;

    if (revents & EV_WRITE)
        client_write_event(EV_A_ w, c);
}
